#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <getopt.h>
#include <netinet/in.h>

#include <fcntl.h>
#include <pthread.h>
#include <sys/eventfd.h>
#include <stdlib.h>
#include <sys/sysinfo.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "log.h"

#include "client_socket.h"
#include "pkg_define.h"

#define MAX_BUF_LEN 1024

using namespace std;

char fifo_name_pre[50] = "/tmp/fifo_cli";
int fd_fifo = -1;

bool g_fifo_running = true;
bool g_running = true;

bool register_statu = false;

IpcClientBase ipc_client;

typedef enum PkgClientType {
  PKG_CLI_MSG = PKG_TYPE_NUM,
  PKG_CLI_EXE_CMD,
  PKG_CLI_TYPE_NUM
} PkgClientType;

typedef struct cmdStr {
  char short_str[10];
  char long_str[80];
} cmdStr;

cmdStr g_cmd_str[PKG_CLI_TYPE_NUM] = {
  ".1", "to register [TBD]",                  // PKG_REGISTER
  ".2", "set up chat with name. eg: .2 name.",       // PKG_SET_TO_SEND
  ".3", "get list online",                    // PKG_GET_LIST
  ".4", "exec cmd on server. eg: .2 ls.",     // PKG_EXE_CMD
  "NULL",  "",                                // PKG_RET_TO_CLI
  "NULL",  "",                                // PKG_SHOW
  "NULL",  "",                                // PKG_CLIENT_DEF  // end of ser cmd
  ".5", "send msg to aother client",          // PKG_CLI_MSG
  ".6", "exec cmd on aother client",          // PKG_CLI_EXE_CMD
};

int show_client_help() {
  LOG_PRINT("help:\n");
  for (int i = 0; i < PKG_CLI_TYPE_NUM; i++) {
    if (strcmp(g_cmd_str[i].short_str, "NULL") == 0) {
      continue;
    }
    LOG_PRINT("%s-->%s\n", g_cmd_str[i].short_str, g_cmd_str[i].long_str);
  }

  return 0;
}

void signalstop(int sign_no) {
  UNUSED(sign_no);
  g_fifo_running = false;
  g_running = false;
}


void print_header(PkgHeader *header) {
  LOG_PRINT("header info:\n");
  // printf("from:   %s\n", header->from);
  // printf("to:     %s\n", header->to);
  LOG_PRINT("type:   %d\n", header->type);
  LOG_PRINT("length: %d\n", header->length);
}

//TODO remove space
//TODO check str on client or server
int send_cmd_ret(char *buff) {
  PkgHeader header;
  memset(&header, 0, sizeof(PkgHeader));
  header.type = PKG_CLIENT_DEF;
  header.client_type = PKG_CLI_MSG;

  do {
    FILE *fp = popen(buff, "r");
    if (fp == NULL) {
      LOG_PERROR("[cli] exe cmd failed");
      break;
    }

    char cmd_tmp[256] = {0};
    // int cmd_tmp_len = 0;
    while(fgets(cmd_tmp, static_cast<size_t>(256), fp) != NULL) {
      header.length = static_cast<int>(strlen(cmd_tmp));
      ipc_client.Send(&header, cmd_tmp, header.length);

      // //每次读取一行
      // int len = strlen(cmd_tmp);
      // strncpy(&dataDesc.buff[cmd_tmp_len], cmd_tmp, len);
      // cmd_tmp_len += len;
      memset(cmd_tmp, 0, 256);
    }
    pclose(fp);
    fp = NULL;
  } while (0);

  return 0;
}


int recv_CB(char *data, int len) {
  PkgHeader header;
  int header_size = (int)sizeof(PkgHeader);
  memset(&header, 0, static_cast<size_t>(header_size));
  memcpy((void *)&header, (void *)data, static_cast<size_t>(header_size));
  //print_header(&header);
  LOG_PRINT("head:[%d] + body[%d]\n", header_size, len - header_size);

  bool client_type = false;
  switch (header.type) {
  case PKG_RET_TO_CLI: {
    if (len - header_size < (int)strlen(STR_RET_LENGTH)) {
      LOG_ERROR("[cli] PKG_REGISTER_RET buffer size err\n");
    } else if (strncmp(&data[header_size], STR_REGISTER_SUCCEED,
        strlen(STR_REGISTER_SUCCEED)) == 0) {
      LOG_INFO("[cli] REGISTER_SUCCEED\n");
      register_statu = true;
    } else if (strncmp(&data[header_size], STR_REGISTER_FAILED,
        strlen(STR_REGISTER_FAILED)) == 0) {
      LOG_INFO("[cli] REGISTER_FAILED\n");
      register_statu = false;
    } else if (strncmp(&data[header_size], STR_SET_TO_SEND_SUCCEED,
        strlen(STR_SET_TO_SEND_SUCCEED)) == 0) {
      LOG_INFO("[cli] SET_TO_SEND_SUCCEED\n");
    } else if (strncmp(&data[header_size], STR_SET_TO_SEND_FAILED,
        strlen(STR_SET_TO_SEND_FAILED)) == 0) {
      LOG_INFO("[cli] SET_TO_SEND_FAILED\n");
    } else {
      LOG_INFO("[cli] PKG_RET_TO_CLI unknow\n");
    }
  } break;

  case PKG_SHOW: {
    LOG_INFO("[cli] PKG_SHOW\n");
    //printf("%.*s\n", len - header_size, &data[header_size]);
    LOG_PRINT("%s\n", &data[header_size]); //TODO end is \0 in client_socket.cpp
// %.*s 其中的.*表示显示的精度 对字符串输出(s)类型来说就是宽度
// 这个*代表的值由后面的参数列表中的整数型(int)值给出
  } break;

  case PKG_CLIENT_DEF: {
    client_type = true;
  } break;

  default: break;
  }

  if (client_type == true) {
    switch (header.client_type) {
    case PKG_CLI_MSG: {
      printf("[cli] PKG_CLI_MSG\n");
      //printf("%.*s\n", len - header_size, &data[header_size]);
      printf("%s\n", &data[header_size]);
    } break;
    case PKG_CLI_EXE_CMD: {
      printf("[cli] PKG_CLI_EXE_CMD\n");
      send_cmd_ret(&data[sizeof(PkgHeader)]);
    } break;

    default: break;
    }
  }

  static unsigned int i_rece = 0;
  i_rece++;
  LOG_DEBUG("***********************************rece count is %d\n", i_rece);
  return 0;
}

static void usage(int argc, char *argv[]) {
  UNUSED(argc);
  printf("%s usage:\n", argv[0]);
  printf("\t -I:  into input mode.\n");
  printf("\t -i:  input server IP addr, default 127.0.0.1\n");
  printf("\t -p:  input server portm default 10500.\n");
  printf("\t -n:  input client name.\n");
  printf("\t -h:  for help.\n");
}

void input_loop(const char *fifo_name) {
  int fd = open(fifo_name, O_WRONLY);
  if (fd < 0) {
    LOG_PERROR("[cli] open failed");
    return;
  }

  fd_set allset;
  fd_set fdset;
  FD_ZERO(&allset);
  FD_ZERO(&fdset);
  FD_SET(STDIN_FILENO, &allset);
  int max_fd = STDIN_FILENO;

  printf("input '.help' to get help\n");
  while (g_fifo_running) {
    fdset = allset;
    int nready = select(max_fd+1, &fdset, NULL, NULL, NULL);
    if (nready <= 0) {
      LOG_PERROR("[cli] select error");
      break;
    }

    if (FD_ISSET(STDIN_FILENO, &fdset)) {
      char buf[256] = {0};
      if (fgets(buf, 256, stdin) == NULL) {
        LOG_PERROR("[cli] fgets error: ");
      }

      if (buf[0] == '\n') {
        continue;
      }

      int len_tmp = static_cast<int>(strlen(buf));
      if (buf[len_tmp - 1] == '\n') {
        buf[len_tmp - 1] = '\0';
      }

      if (write(fd, buf, sizeof(buf)) < 0) {
        LOG_PERROR("[cli] fifo write");
      }
    }
  }
  close(fd);
  printf("[cli] input mode exit\n");
}

static const char *short_options = "Ip:i:n:h";
static struct option long_options[] = {
    {"input mode",      required_argument, 0, 'I'},
    {"connect port",    required_argument, 0, 'p'},
    {"connect IP",      required_argument, 0, 'i'},
    {"name",            required_argument, 0, 'n'},
    {"help",            required_argument, 0, 'h'},
    {0, 0, 0, 0}
};

int main(int argc, char *argv[]) {
  printf("in main\n");

  signal(SIGINT, signalstop);
  signal(SIGQUIT, signalstop);
  signal(SIGTERM, signalstop);

  log_init();

  int port = 10500;
  string ip("127.0.0.1");
  char name[20] = "null";

  //for debug
  bool input_mode = false;

  int ch = 0;
  int option_index = 0;
  while ((ch = getopt_long(argc, argv, short_options, long_options,
      &option_index)) != -1) {
    switch (ch) {

    case 'I':
      input_mode = true;
      break;

    case 'p':
      port = atoi(optarg);
      break;

    case 'i':
      ip = string(optarg);
      break;

    case 'n':
      strcpy(name, optarg);
      break;

    case 'h':
      usage(argc, argv);
      return 0;

    default:
      usage(argc, argv);
      return 0;
    }
  }

  if (strcmp(name, "null") == 0) {
    printf("please input name\n");
    usage(argc, argv);
    return 0;
  }

  char fifo_name[126] = {0};
  sprintf(fifo_name, "%s_%s", fifo_name_pre, name);

  if (input_mode == true) {
    input_loop(fifo_name);
    return 0;
  }

  if (access(fifo_name, F_OK) == 0) {
    if (unlink(fifo_name) < 0) {
      LOG_PERROR("[cli] unlink failed!");
    }
  }
  int ret = mkfifo(fifo_name, 0777);
  if (ret < 0) {
    LOG_PERROR("[cli] mkfifo failed!\n");
    return -1;
  }
  fd_fifo = open(fifo_name, O_RDWR);
  if (fd_fifo < 0) {
    LOG_PERROR("[cli] open failed");
    return -1;
  }

  //connect
  ipc_client.SetCb(&recv_CB);
  if (ipc_client.ConnectServer(ip.c_str(), port) < 0) {
    printf("ipc_client.connectServer failed.\n");
    return -1;
  }

  fd_set allset;
  fd_set fdset;
  FD_ZERO(&allset);
  FD_ZERO(&fdset);
  FD_SET(fd_fifo, &allset);
  int max_fd = fd_fifo;

  //register
  PkgHeader header;
  memset(&header, 0, sizeof(PkgHeader));
  header.type = PKG_REGISTER;
  header.length = static_cast<int>(strlen(name));
  ipc_client.Send(&header, name, header.length);

  //TODO
  //log in
  //username
  //password
  int i_register = 0;
  for (i_register = 0; i_register < 30; i_register++) {
    if (register_statu == true) {
      break;
    }
    usleep(100000); // 100ms * 30 = 3s
  }
  if (i_register >= 10) {
    printf("[cli] register faield\n");
    ipc_client.Disconnect();

    if (access(fifo_name, F_OK) == 0) {
      if (unlink(fifo_name) < 0) {
        LOG_PERROR("unlink failed!");
      }
    }

    return -1;
  }
  printf("[cli] register success\n");

  //in main process, 'ctrl+c'(SIGNALINT) will interrupt select,
  // so no deal with this statu
  while (g_running) {
    fdset = allset;
    int nready = select(max_fd+1, &fdset, NULL, NULL, NULL);
    if (nready <= 0) {
      LOG_PERROR("[cli] select error");
      break;
    }

    if (FD_ISSET(fd_fifo, &fdset)) {
      char buf[256] = {0};
      if (read(fd_fifo, buf, 256) < 0) {
        LOG_PERROR("[cli] fifo read:");
        continue;
      }

      printf("[cli] fifo read succeed\n");
      memset(&header, 0, sizeof(PkgHeader));
      //char body[MAX_BUF_LEN] = {0};
      if (strncmp(buf, ".help", strlen(".help")) == 0) {
        show_client_help();
      } else if (strncmp(buf, g_cmd_str[PKG_REGISTER].short_str,
          strlen(g_cmd_str[PKG_REGISTER].short_str)) == 0) {
        printf("input .1\n");
        //header.type = PKG_REGISTER;
      } else if (strncmp(buf, g_cmd_str[PKG_SET_TO_SEND].short_str,
          strlen(g_cmd_str[PKG_SET_TO_SEND].short_str)) == 0) {
        printf("input .2\n");
        //TODO remove space.
        //TODO check str on client or server
        // 1 is space
        int cmd_len = static_cast<int>(
            strlen(g_cmd_str[PKG_SET_TO_SEND].short_str) + 1);
        header.type = PKG_SET_TO_SEND;
        if (cmd_len < (int)strlen(buf)) {
          header.length = static_cast<int>(strlen(buf)) - cmd_len;
          ipc_client.Send(&header, &buf[cmd_len], header.length);
        } else {
          printf("input to_name err\n");
        }
      }
      else if (strncmp(buf, g_cmd_str[PKG_GET_LIST].short_str,
          strlen(g_cmd_str[PKG_GET_LIST].short_str)) == 0) {
        printf("input .3\n");
        header.type = PKG_GET_LIST;
        header.length = 0;
        ipc_client.Send(&header, NULL, header.length);
      } else if (strncmp(buf, g_cmd_str[PKG_EXE_CMD].short_str,
          strlen(g_cmd_str[PKG_EXE_CMD].short_str)) == 0) {
        printf("input .4\n");
        //TODO remove space
        //TODO check str on client or server
        // 1 is space
        int cmd_len = static_cast<int>(
            strlen(g_cmd_str[PKG_EXE_CMD].short_str) + 1);
        header.type = PKG_EXE_CMD;
        //header.length = 0;
        //ipc_client.send(&header, NULL, header.length);
        if (cmd_len < static_cast<int>(strlen(buf))) {
          header.length = static_cast<int>(strlen(buf)) - cmd_len;
          ipc_client.Send(&header, &buf[cmd_len], header.length);
        } else {
          printf("input exe cmd err\n");
        }
      } else if (strncmp(buf, g_cmd_str[PKG_CLI_EXE_CMD].short_str,
          strlen(g_cmd_str[PKG_CLI_EXE_CMD].short_str)) == 0) {
        printf("input .5\n");
        header.type = PKG_CLIENT_DEF;
        header.client_type = PKG_CLI_EXE_CMD;

        // 1 is space
        int cmd_len = static_cast<int>(
            strlen(g_cmd_str[PKG_CLI_EXE_CMD].short_str) + 1);
        if (cmd_len < static_cast<int>(strlen(buf))) {
          header.length = static_cast<int>(strlen(buf)) - cmd_len;
          ipc_client.Send(&header, &buf[cmd_len], header.length);
        } else {
          printf("input cli exe cmd err\n");
        }
      } else {  //default is seng msg each other
        header.type = PKG_CLIENT_DEF;
        header.client_type = PKG_CLI_MSG;
        header.length = static_cast<int>(strlen(buf));
        ipc_client.Send(&header, buf, header.length);
      }
    }
  }

  if (access(fifo_name, F_OK) == 0) {
    if (unlink(fifo_name) < 0) {
      LOG_PERROR("unlink failed!");
    }
  }

  ipc_client.Disconnect();
  log_deinit();
  printf("[cli] main exit\n");

  return 0;
}
